#!/usr/bin/python
##   Copyright 2007, Michael McKinley (m.mckinley [at] gmail [dot] com)
##
##    This program is free software; you can redistribute it and/or modify
##    it under the terms of the GNU General Public License as published by
##    the Free Software Foundation; either version 3 of the License, or
##    (at your option) any later version.
##
##    This program is distributed in the hope that it will be useful,
##    but WITHOUT ANY WARRANTY; without even the implied warranty of
##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##    GNU General Public License for more details.
##
##    You should have received a copy of the GNU General Public License
##    along with this program.  If not, see <http://www.gnu.org/licenses/>.
##

import poplib,smtplib
from re import search
from urllib2 import urlopen
from ConfigParser import SafeConfigParser
import sys

#Various encryption 'modes' for SMTP
TLSMODES = ('GUESS', 'NEVER', 'FORCE', 'SSL')
#Implement a very basic enum by dynamically creating a dictionary that maps each
# value of TLSMODES to an integer.
TLSMODES = dict(zip(TLSMODES, range(0, len(TLSMODES))))

#This is a little script that will check a specified email for messages.  If any
# exist, it will reply with this computer's public IP (based on what
# whatismyip.org says).  Make this a cronjob.
#
#I mainly wrote this for  myself and so I've only bothered to test it against
# the three POP and SMTP servers I have access to/use (gmail is known to work).
# It should work with *most* servers - it does semi-intelligent guessing to 
# determine what language the server uses.
#
#If you have a problem please send me info, I would very much appreciate it.
#
#All libraries should be included with your version of Python (tested on 2.5)

if len(sys.argv) != 2:
   print "Syntax: %s /path/to/config" % sys.argv[0]

email = 'm.mckinley.backup@gmail.com'
username = email                 #Change this if different than your email
password = '24nnys785.Gpo'       # many hosts will accept your full email as a
                                 # username (and some, like gmail, require it).
pop_host = 'pop.gmail.com'
pop_port = 995
pop_use_ssl = True               #Check with your host whether or not you can 
                                 # (or should) use SSL.  Generally, if you are
                                 # able to, you should.

#Note: at this time, ssl'd smtp connections are not supported.
smtp_host = 'smtp.gmail.com'
smtp_port = 587
smtp_use_tls= TLSMODES['FORCE']  #Valid options:
                                 #  * GUESS - Query the server and use TLS if
email_whitelist = ()             #    available
                                 #  * FORCE/NEVER - Always/never use TLS
                                 #  * SSL - Use SSL encryption

popcreds = (username, password)  #If you have different credentials for the
smtpcreds = (username, password) # servers, change it here.  The login details 
                                 # will nearly always be the same unless your
                                 # host sucks or you are using seperate servers


class POPClient:
   def __init__(self, credentials, host, port, ssl = False):
      self.user = credentials[0]
      self.pwd = credentials[1]
      self.server = (host, port)   
      self.ssl = ssl
      self.connection = self.get_connection()
   
   #Return either a POP3 or POP3_SSL object depending on what sort of connection
   # the server needs / client wants.
   def get_connection(self):
      if self.ssl:
         return poplib.POP3_SSL(self.server[0], self.server[1])
      else:   
         return poplib.POP3(self.server[0], self.server[1])

   #Login.  Returns TRUE iff the login was successful.
   def login(self):
      try:
         #First try logging in through the (better) apop method
         r = self.connection.apop(user, pwd)
         return r.startswith('+OK')
      
      #If that fails (an exception will be thrown), use the vanilla method      
      except:
         #Note, while putting this in a try/except block sounds like a good idea
         # I deliberately didn't because any exception would have useful info
         # that I would like to see.
         r = self.connection.user(self.user)
         if r.startswith('+OK'):
            r = self.connection.pass_(self.pwd)
            return r.startswith('+OK')
   
   #Return a list containing all the email addresses this user has an email from
   def get_senders(self):
      senders = []
      for i in xrange(1, self.get_messagecount() + 1):
         msg = self.connection.retr(i)[1]
         sender = self.get_sender(msg)
         if sender not in senders:     #Ensure we don't have duplicates
            senders.append(sender)
         self.connection.dele(i)       #Delete the message, since it's no longer
                                       # useful.
      return senders

   def end(self):
      self.connection.quit()
   
   def get_messagecount(self):
      return len(self.connection.list()[1])

   def get_sender(self, msg):
      for field in msg:
         if field.startswith("From:"):
            a = search("<(.*?)>", field)
            return a.group(1)

class SMTPClient:
   def __init__(self, creds, email, host, port, use_tls):
      self.user = smtpcreds[0]
      self.pwd = smtpcreds[1]
      self.email = email
      self.server = "%s:%s" % (host, port)      
      self.connection = smtplib.SMTP()    #Don't connect yet, we want to grab
                                          # the output so we can see if it uses
                                          # ESMTP so we know whether to use HELO
                                          # or EHLO.
      self.tlsmode = use_tls
      self.helloer = self.connect()

   #Connect to the server and return the appropriate HELO/EHLO method.
   def connect(self):
      r = self.connection.connect(self.server)
      if r[0]/100 != 2:
         return False      
      if 'ESMTP' in r[1]:
         return smtplib.SMTP.ehlo
      else:
         return smtplib.SMTP.helo

   def login(self):
      if not self.helloer:
         return False
      #HELO or EHLO the server
      self.helloer(self.connection, self.email)
      
      #Use TLS if needed and (?:HE|EH)LO again
      if self.tlsmode == TLSMODES['FORCE'] or\
      (self.tlsmode == TLSMODES['GUESS'] and a.has_extn('STARTTLS')):
         self.connection.starttls()
         self.helloer(self.connection, self.email)
      
      #Log in
      r = self.connection.login(self.user, self.pwd)

      #All "OK" response codes are in the 200's, so check if the hundreds-digit
      # is a 2.  If so, everything is shiny.   

      if r[0]/100 != 2: 
         return False    
      return True

   def sendmsg(self, toemail, message):
      self.connection.sendmail(self.email, toemail, message)

pc = POPClient(popcreds, pop_host, pop_port, pop_use_ssl)
sc = SMTPClient(smtpcreds, email, smtp_host, smtp_port, smtp_use_tls)

print "Attempting login"
if not pc.login():
   print "Sorry, couldn't log in to POP server"
   exit()

if not sc.login():
   print "Sorry could not log in to SMTP server"
   exit()

emails = pc.get_senders()
pc.end()

if emails:
   ip = urlopen('http://www.whatismyip.org/').read()
   print "My IP is %s" % ip
   for email in emails:
      print "Message from %s" % email
      if len(email_whitelist) == 0 or (email in email_whitelist):
         sc.sendmsg(email, ip)
         print "IP sent to %s" % email
      else:
         print "%s not on whitelist!"
else:
   print "No emails in inbox, doing nothing"
